#include "Pch.h"

#include "TriangleBuffer.h"
#include "../../MCD/Render/Mesh.h"
#include "../../MCD/Render/Color.h"

#include <map>
#include <list>
#include <vector>

namespace IS
{

class TriangleBuffer::Impl
{
public:
	typedef TriangleBuffer::Vertex Vertex;

	struct Record { Vertex v; size_t idx; };

	typedef std::list<Record> RecordList;
	typedef std::map<int, RecordList> Cache;

	Cache mCache;
	std::vector<Vertex> mVertexArray;
	std::vector<size_t> mIndexArray;
	MeshPtr mMesh;

	Vec3f* mPosPtr;
	Vec3f* mNormPtr;
	Vec3f* mTangPtr;
	Vec2f* mUV0Ptr;
	Vec2f* mUV1Ptr;
	Vec4f* mColorPtr;
	uint16_t* mIndexPtr;
	size_t mIndexCnt;

	size_t addVertex(int hash, const Vertex& vertex)
	{
		RecordList& recList = mCache[hash];

		for(RecordList::iterator ivert = recList.begin(); ivert != recList.end(); ++ivert)
		{
			if(::memcmp(&ivert->v, &vertex, sizeof(Vertex)) == 0)
				return ivert->idx;
		}

		Record rec;
		rec.v = vertex;
		rec.idx = mVertexArray.size();

		recList.push_back(rec);
		mVertexArray.push_back(vertex);
		
		return rec.idx;
	}
};

TriangleBuffer::TriangleBuffer()
	: mImpl(new Impl)
{
	localTransform = Mat44f::cIdentity;
}

TriangleBuffer::~TriangleBuffer()
{
	delete mImpl;
}

void TriangleBuffer::beginDefine()
{
}

void TriangleBuffer::defineTriangle(int hash[3], Vertex vertex[3])
{
	mImpl->mIndexArray.push_back(mImpl->addVertex(hash[0], vertex[0]));
	mImpl->mIndexArray.push_back(mImpl->addVertex(hash[1], vertex[1]));
	mImpl->mIndexArray.push_back(mImpl->addVertex(hash[2], vertex[2]));
}

void TriangleBuffer::endDefine()
{
	if(mImpl->mVertexArray.size() >= 0x0000ffff)
	{
		Log::format(Log::Error, L"TriangleBuffer: too many vertices %d (max. %d)!!", mImpl->mVertexArray.size(), 0xffff);
		return;
	}

	clear();

	enable(
		Mesh::Position | Mesh::Normal	// position, normal
		| Mesh::TextureCoord3			// uv0, uv1, tangent, and color
		| Mesh::Index);

	textureUnit(Mesh::TextureCoord0);
	textureCoordSize(2);

	textureUnit(Mesh::TextureCoord1);
	textureCoordSize(2);

	textureUnit(Mesh::TextureCoord2);
	textureCoordSize(3);

	textureUnit(Mesh::TextureCoord3);
	textureCoordSize(4);

	for(size_t ivert = 0; ivert < mImpl->mVertexArray.size(); ++ivert)
	{
		Vertex &vert = mImpl->mVertexArray[ivert];

		position(vert.pos);
		normal(vert.normal);

		textureUnit(Mesh::TextureCoord0);
		textureCoord(vert.uv0);

		textureUnit(Mesh::TextureCoord1);
		textureCoord(vert.uv1);

		textureUnit(Mesh::TextureCoord2);
		textureCoord(vert.tangent);

		textureUnit(Mesh::TextureCoord3);
		textureCoord(vert.color);

		uint16_t idx = addVertex();

		MCD_VERIFY(idx == (uint16_t)ivert);
	}

	MCD_VERIFY(0 == mImpl->mIndexArray.size() % 3);

	for(size_t iface = 0; iface < mImpl->mIndexArray.size();)
	{
		uint16_t i0 = uint16_t(mImpl->mIndexArray[iface++]);
		uint16_t i1 = uint16_t(mImpl->mIndexArray[iface++]);
		uint16_t i2 = uint16_t(mImpl->mIndexArray[iface++]);
		this->addTriangle(i0, i1, i2);
	}

	// commit to mesh
	mImpl->mMesh = new Mesh;
	commit(*mImpl->mMesh, Static);

	// finally release temp memory
	{ Impl::Cache tmp; std::swap(tmp, mImpl->mCache); }
	{ std::vector<Vertex> tmp; std::swap(tmp, mImpl->mVertexArray); }
	{ std::vector<size_t> tmp; std::swap(tmp, mImpl->mIndexArray); }
}

void TriangleBuffer::beginUpdate()
{
	mImpl->mPosPtr		= (Vec3f*)acquireBufferPointer(Mesh::Position);
	mImpl->mNormPtr		= (Vec3f*)acquireBufferPointer(Mesh::Normal);
	mImpl->mUV0Ptr		= (Vec2f*)acquireBufferPointer(Mesh::TextureCoord0);
	mImpl->mUV1Ptr		= (Vec2f*)acquireBufferPointer(Mesh::TextureCoord1);
	mImpl->mTangPtr		= (Vec3f*)acquireBufferPointer(Mesh::TextureCoord2);
	mImpl->mColorPtr	= (Vec4f*)acquireBufferPointer(Mesh::TextureCoord3);
	mImpl->mIndexPtr	= (uint16_t*)acquireBufferPointer(Mesh::Index, &mImpl->mIndexCnt);
}

size_t TriangleBuffer::getTriangleCount() const
{
	MCD_VERIFY(mImpl->mIndexPtr != nullptr);
	return mImpl->mIndexCnt / 3;
}

uint16_t* TriangleBuffer::getTriangleIndexAt(size_t face)
{
	MCD_VERIFY(mImpl->mIndexPtr != nullptr);
	return &mImpl->mIndexPtr[face*3];
}

Vec3f& TriangleBuffer::getPositionAt(uint16_t vertexIndex)
{
	MCD_VERIFY(mImpl->mPosPtr != nullptr);
	return mImpl->mPosPtr[vertexIndex];
}

Vec3f& TriangleBuffer::getNormalAt(uint16_t vertexIndex)
{
	MCD_VERIFY(mImpl->mNormPtr != nullptr);
	return mImpl->mNormPtr[vertexIndex];
}

Vec3f& TriangleBuffer::getTangentAt(uint16_t vertexIndex)
{
	MCD_VERIFY(mImpl->mTangPtr != nullptr);
	return mImpl->mTangPtr[vertexIndex];
}

Vec2f& TriangleBuffer::getUV0At(uint16_t vertexIndex)
{
	MCD_VERIFY(mImpl->mUV0Ptr != nullptr);
	return mImpl->mUV0Ptr[vertexIndex];
}

Vec2f& TriangleBuffer::getUV1At(uint16_t vertexIndex)
{
	MCD_VERIFY(mImpl->mUV1Ptr != nullptr);
	return mImpl->mUV1Ptr[vertexIndex];
}

Vec4f& TriangleBuffer::getColorAt(size_t vertexIndex)
{
	MCD_VERIFY(mImpl->mColorPtr != nullptr);
	return mImpl->mColorPtr[vertexIndex];
}

void TriangleBuffer::endUpdate()
{
#define _RELEASE_PTR(ptr) releaseBufferPointer(ptr); ptr = nullptr;

	_RELEASE_PTR(mImpl->mPosPtr);
	_RELEASE_PTR(mImpl->mNormPtr);
	_RELEASE_PTR(mImpl->mUV0Ptr);
	_RELEASE_PTR(mImpl->mUV1Ptr);
	_RELEASE_PTR(mImpl->mTangPtr);
	_RELEASE_PTR(mImpl->mColorPtr);
	_RELEASE_PTR(mImpl->mIndexPtr);

#undef _RELEASE_PTR
}

void TriangleBuffer::draw()
{
	if(nullptr != mImpl->mMesh)
		mImpl->mMesh->draw();
}

}